package org.irri.smta.ui.bean;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Serializable;
import java.io.Writer;
import java.net.URL;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;

import javax.annotation.PostConstruct;
import javax.faces.bean.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.irri.smta.common.Constants;
import org.irri.smta.domain.ReportLog;
import org.irri.smta.domain.SmtaReport;
import org.irri.smta.report.Smta;
import org.irri.smta.report.result.SmtaReportResult;
import org.irri.smta.report.result.SmtaReportResultBuilder;
import org.irri.smta.service.IReportLogService;
import org.irri.smta.service.ISmtaReportService;
import org.irri.smta.service.ISmtaTransformerService;
import org.irri.smta.util.BeanUtils;
import org.irri.smta.util.FileUtils;
import org.irri.smta.util.HttpPostUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Named("reporterBean")
@SessionScoped
public class ReporterBean implements Serializable {

	private static final long serialVersionUID = 1L;
	private static final Logger LOGGER = LoggerFactory
			.getLogger(ReporterBean.class);

	@Inject
	@Named("loginBean")
	private LoginBean loginBean = null;

	@Inject
	@Named("resourceConfigBean")
	private ResourceConfigBean resourceConfigBean;

	@Inject
	@Named("smtaTransformerService")
	private ISmtaTransformerService smtaTransformerService;

	@Inject
	@Named("smtaReportService")
	private ISmtaReportService<SmtaReport, Long> smtaReportService;

	@Inject
	@Named("reportLogService")
	private IReportLogService<ReportLog, Long> reportLogService;

	private String viewName;
	private Integer progress = 0;
	private Integer successCount = 0;
	private Integer failedCount = 0;
	private Boolean abort = true;
	private HttpPostUtils httpPostUtils = new HttpPostUtils();
	private BeanUtils beanUtils = new BeanUtils();
	private static final Integer SLEEP_INTERVAL_MS = 500;

	public ReporterBean() {
		super();
		setViewName(Constants.REPORTER_VIEW_NAME);
	}

	@PostConstruct
	public void init() {
		LOGGER.debug("init()");
		progress = 0;
		successCount = 0;
		failedCount = 0;
		abort = false;
	}

	public ISmtaTransformerService getSmtaTransformerService() {
		return smtaTransformerService;
	}

	public void setSmtaTransformerService(
			ISmtaTransformerService smtaTransformerService) {
		this.smtaTransformerService = smtaTransformerService;
	}

	public ISmtaReportService<SmtaReport, Long> getSmtaReportService() {
		return smtaReportService;
	}

	public void setSmtaReportService(
			ISmtaReportService<SmtaReport, Long> smtaReportService) {
		this.smtaReportService = smtaReportService;
	}

	public String getViewName() {
		return viewName;
	}

	public void setViewName(String viewName) {
		this.viewName = viewName;
	}

	public void preRenderView() {
		LOGGER.debug("preRenderView()");
		if (beanUtils.getSessionUser() == null) {
			loginBean.defaultLogin();
		}
		if (!beanUtils.validateUser()) {
			beanUtils.navigateToView(Constants.LOGIN_VIEW_NAME);
		}
	}

	public Integer getProgress() {
		if (progress == null) {
			progress = 0;
			successCount = 0;
			failedCount = 0;
		} else {
			if (progress > 100) {
				progress = 100;
			}
		}

		return progress;
	}

	public void setProgress(Integer progress) {
		this.progress = progress;
	}

	public void onComplete() {
		beanUtils.updateInfoMessage("Completed.", "Successful: " + successCount
				+ ", Failed: " + failedCount);
	}

	public void start() {
		progress = 0;
		successCount = 0;
		failedCount = 0;
		abort = false;
		processUnreportedSmtas();
	}

	public void cancel() {
		progress = null;
		abort = true;
	}

	public static Boolean isXmlLike(String xmlStr) {
		LOGGER.debug("Boolean isXmlLike(String xmlStr)");
		Boolean returnValue = false;
		Pattern pattern;
		Matcher matcher;

		// A regular expression to check if the String at least starts and ends
		// with the same element.
		final String XML_PATTERN_STR = "<(\\S+?)(.*?)>(.*?)</\\1>";

		if (xmlStr != null && xmlStr.trim().length() > 0) {
			// Does the string resemble XML?
			if (xmlStr.trim().startsWith("<")) {
				if (xmlStr.startsWith("<?xml")) {
					return true;
				}
				pattern = Pattern.compile(XML_PATTERN_STR,
						Pattern.CASE_INSENSITIVE | Pattern.DOTALL
								| Pattern.MULTILINE);

				// Return true if pass both tests
				matcher = pattern.matcher(xmlStr);
				returnValue = matcher.matches();
			}
		}

		return returnValue;
	}
	
	public void processUnreportedSmta(SmtaReport smtaReport) {
		try {
			if (abort) {
				return;
			}
			Smta smta = smtaTransformerService
					.transformSmtaReportToSmta(smtaReport);

			String tempDir = System.getProperty("java.io.tmpdir");
			if (!(tempDir.endsWith("/") || tempDir.endsWith("\\"))) {
				tempDir = tempDir + System.getProperty("file.separator");
			}

			StringBuffer pdfUrl = new StringBuffer();
			if (!(smtaReport.getPdfUrl().startsWith("file:") || smtaReport.getPdfUrl().startsWith("http:"))) {
				pdfUrl.append("file://");
			}
			
			// Append the PDF generated directory
			pdfUrl.append(resourceConfigBean.getGeneratedDirectory()).append("/");
			
			pdfUrl.append(smtaReport.getPdfUrl());
			if (StringUtils.isEmpty(pdfUrl.toString())) {
				throw new Exception("No PDF location specified");
			}
			URL url = new URL(pdfUrl.toString());

			File pdfFile = null;
			byte[] bytes = null;
			if (("http").equalsIgnoreCase(url.getProtocol())) {
				String tempPdfFilename = String.format("%s.%s",
						RandomStringUtils.randomAlphanumeric(8), "pdf");
				File tempPdfFile = new File(tempDir + tempPdfFilename);
				FileUtils.downloadFile(url.toString(), tempPdfFile);

				bytes = FileUtils.fileToByteArray(tempPdfFile);

				tempPdfFile.delete();

			} else {
				try {
					pdfFile = new File(url.toURI());
				} catch (Exception e) {
					pdfFile = new File(url.getPath());
				}
				if (pdfFile == null || !pdfFile.exists()
						|| !pdfFile.isFile() || !pdfFile.canRead()) {
					throw new Exception("Error reading PDF file!");
				}

				bytes = FileUtils.fileToByteArray(pdfFile);
			}

			smta.getDocument().setPdf(bytes);

			String xml = smtaTransformerService
					.transformSmtaReportToXml(smta);

			String tempFilename = String.format("%s.%s",
					RandomStringUtils.randomAlphanumeric(8), "xml");
			File tempFile = new File(tempDir + tempFilename);

			Writer writer = new BufferedWriter(new OutputStreamWriter(
					new FileOutputStream(tempFile), "UTF8"));
			writer.append(xml);
			writer.flush();
			writer.close();

			String gzipFilename = String
					.format("%s.%s", tempFilename, "gz");
			File gzipFile = new File(tempDir + gzipFilename);
			GZIPOutputStream gzos = new GZIPOutputStream(
					new FileOutputStream(gzipFile));
			FileInputStream fin = new FileInputStream(tempFile);
			int len;
			byte[] buffer = new byte[1024];
			while ((len = fin.read(buffer)) > 0) {
				gzos.write(buffer, 0, len);
			}
			fin.close();
			gzos.finish();
			gzos.close();

			String result = null;
			try {
				StringBuffer sbuf = new StringBuffer(
						resourceConfigBean.buildUrl(
								resourceConfigBean.getUrlTreatyScheme(),
								resourceConfigBean.getUrlTreatyHost(),
								resourceConfigBean.getUrlTreatyPort(),
								resourceConfigBean.getUrlTreatyPath()));
				sbuf.append("?");
				sbuf.append(resourceConfigBean.getUrlTreatyQueryString());
				String urlString = sbuf.toString();
				String username = resourceConfigBean.getUrlTreatyUsername();
				String password = resourceConfigBean.getUrlTreatyPassword();

				if (resourceConfigBean.getUrlTreatyIsReportingOn()) {
					result = httpPostUtils.postFile(gzipFile, urlString,
							username, password, true);
				} else {
					throw new Exception("Reporting is off.");
				}

				// Check if the result is XML-like
				if (isXmlLike(result)) {
					SmtaReportResultBuilder smtaReportResultBuilder = new SmtaReportResultBuilder();
					SmtaReportResult smtaReportResult = smtaReportResultBuilder
							.unmarshalXml(result);
					// Check if result is "OK" or not
					if (("OK").equals(smtaReportResult.getResult())) {
						// Update reporting status
						smtaReportService.updateReportedToTreaty(smtaReport.getId(), true);
						successCount++;
					} else {
						// Result is not OK, so failed
						failedCount++;
					}
				} else {
					// Not XML, so failed
					failedCount++;
				}
			} catch (Exception ex) {
				// Add exception to report log
				reportLogService.addReportLog(ex.getMessage());
				// Got an exception, so failed
				failedCount++;
				LOGGER.error(ex.getMessage());
			} finally {
				// Update the number of reporting attempts for this SMTA
				smtaReportService.incrementReportAttempts(smtaReport.getId());
				// Add result to report log
				if (result != null) {
					reportLogService.addReportLog(result);
				}
			}

			LOGGER.info(result);
			tempFile.delete();
			gzipFile.delete();
		} catch (Exception e) {
			reportLogService.addReportLog(e.getMessage());
			failedCount++;
			LOGGER.error(e.getMessage());
		}
	}

	private void processUnreportedSmtas() {
		LOGGER.debug("processUnreportedSmtas()");
		List<SmtaReport> unreportedList = smtaReportService.findAllUnreported();
		Double total = unreportedList.size() * 1.0;
		Integer counter = 0;
		for (SmtaReport smtaReport : unreportedList) {
			processUnreportedSmta(smtaReport);
			
			counter++;
			// Update percentage completed
			Double ratio = counter / total;
			Integer percent = new Long(Math.round(Math.ceil(ratio * 100)))
					.intValue();
			// Update percentage in progress bar
			setProgress(percent);

			try {
				Thread.sleep(SLEEP_INTERVAL_MS);
			} catch (InterruptedException e) {
				LOGGER.error(e.getMessage());
			}
		}
		setProgress(100);
	}

}
